package ar.edu.itba.it.cg.grupo01.impl.parser;

import java.awt.Color;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.LinkedList;

import javax.vecmath.Matrix4f;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;

import ar.edu.itba.it.cg.grupo01.impl.CameraBuilder;
import ar.edu.itba.it.cg.grupo01.impl.IntersectionFactory;
import ar.edu.itba.it.cg.grupo01.impl.parser.TextfileParser.ParserException;
import ar.edu.itba.it.cg.grupo01.impl.shape.Box;
import ar.edu.itba.it.cg.grupo01.impl.shape.Plane;
import ar.edu.itba.it.cg.grupo01.impl.shape.Sphere;
import ar.edu.itba.it.cg.grupo01.impl.shape.Triangle;
import ar.edu.itba.it.cg.grupo01.impl.shape.TriangleMesh;
import ar.edu.itba.it.cg.grupo01.impl.shape.light.PointLight;
import ar.edu.itba.it.cg.grupo01.impl.shape.shader.ConstantShader;
import ar.edu.itba.it.cg.grupo01.impl.shape.shader.DiffuseShader;
import ar.edu.itba.it.cg.grupo01.impl.shape.shader.FireShader;
import ar.edu.itba.it.cg.grupo01.impl.shape.shader.GlassShader;
import ar.edu.itba.it.cg.grupo01.impl.shape.shader.MarbleShader;
import ar.edu.itba.it.cg.grupo01.impl.shape.shader.MirrorShader;
import ar.edu.itba.it.cg.grupo01.impl.shape.shader.OrganicShader;
import ar.edu.itba.it.cg.grupo01.impl.shape.shader.PerlinShader;
import ar.edu.itba.it.cg.grupo01.impl.shape.shader.PhongShader;
import ar.edu.itba.it.cg.grupo01.impl.shape.shader.StoneShader;
import ar.edu.itba.it.cg.grupo01.impl.shape.shader.WoodShader;

public class SceneParser {

	private TextfileParser p;
	private int numLightSamples;
	private static IntersectionFactory intersectionFactory = new IntersectionFactory();
	private static RGBTransformer transformer = new RGBTransformer(2.4f,
			0.00304f);

	public SceneParser() {

	}

	public boolean parse(String filename, SceneDTO dto) {

		String localDir = new File(filename).getAbsoluteFile().getParentFile()
				.getAbsolutePath();
		numLightSamples = 1;

		System.out.println("Parsing started");
		try {
			p = new TextfileParser(filename);
			while (true) {
				String token = p.getNextToken();
				if (token == null)
					break;
				if (token.equals("image")) {
					System.out.println("Parsing image settings...");
					parseImageBlock(dto);
				} else if (token.equals("camera")) {
					System.out.println("Parsing camera settings...");
					parseCamera(dto);
				} else if (token.equals("shader")) {
					System.out.println("Parsing shaders...");
					if (!parseShader(dto))
						return false;
				} else if (token.equals("object")) {
					System.out.println("Parsing object...");
					parseObjectBlock(dto);
				} else if (token.equals("light")) {
					System.out.println("Parsing light...");
					parseLightBlock(dto);
				} else if (token.equals("texturepath")) {
					System.out.println("Parsing texture path...");
					String path = p.getNextToken();
					if (!new File(path).isAbsolute())
						path = localDir + File.separator + path;
					// FIXME
					// dto.addTextureSearchPath(path);
				} else if (token.equals("include")) {
					String file = localDir + File.separator + p.getNextToken();
					System.out.println("Including: \"" + file + "\" ...");
					parse(file, dto);
				}
			}
			p.close();
		} catch (ParserException e) {
			e.printStackTrace();
			return false;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			return false;
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}

		System.out.println("Done parsing");
		return true;
	}

	private void parseImageBlock(SceneDTO dto) throws ParserException,
			IOException {

		CameraBuilder aCamera;
		if ((aCamera = dto.getCamera()) == null) {
			aCamera = new CameraBuilder();
		}

		p.checkNextToken("{");
		if (p.peekNextToken("resolution")) {
			aCamera.setxResolution(p.getNextInt());
			aCamera.setyResolution(p.getNextInt());
		}
		if (p.peekNextToken("aa")) {
			p.getNextInt();
			dto.setAntiAliasing(p.getNextInt());
		}
		if (p.peekNextToken("samples")) {
			// renderscene.setSamples(p.getNextInt());
			p.getNextInt();
		}
		if (p.peekNextToken("contrast")) {
			// renderscene.setContrast(p.getNextFloat());
			p.getNextFloat();
		}
		if (p.peekNextToken("filter")) {
			// renderscene.setFilter(p.getNextToken());
			p.getNextToken();
		}
		if (p.peekNextToken("jitter")) {
			// renderscene.setJitter(p.getNextBoolean());
			p.getNextBoolean();
		}
		if (p.peekNextToken("show-aa")) {
			System.out.println("Deprecated: show-aa ignored");
			p.getNextBoolean();
		}
		if (p.peekNextToken("output")) {
			System.out.println("Deprecated: output statement ignored");
			p.getNextToken();
		}
		p.checkNextToken("}");

		dto.setCamera(aCamera);
	}

	private void parseCamera(SceneDTO dto) throws ParserException, IOException {

		CameraBuilder aCamera;
		if ((aCamera = dto.getCamera()) == null) {
			aCamera = new CameraBuilder();
		}

		p.checkNextToken("{");
		p.checkNextToken("type");
		String type = p.getNextToken();

		parseCameraTransform(aCamera);

		if (type.equals("pinhole")) {

			p.checkNextToken("fov");
			aCamera.setFov((p.getNextFloat()));

			p.checkNextToken("aspect");
			p.getNextFloat();

		} else if (type.equals("thinlens")) {
			p.checkNextToken("fov");
			aCamera.setFov((p.getNextFloat()));
			p.checkNextToken("aspect");
			p.getNextFloat();

			p.checkNextToken("fdist");
			p.getNextFloat();
			// api.parameter("focus.distance", p.getNextFloat());

			p.checkNextToken("lensr");
			p.getNextFloat();
			// api.parameter("lens.radius", p.getNextFloat());

			if (p.peekNextToken("sides"))
				p.getNextInt();
			// api.parameter("lens.sides", p.getNextInt());

			if (p.peekNextToken("rotation"))
				p.getNextFloat();
			// api.parameter("lens.rotation", p.getNextFloat());

		} else {
			return;
		}
		p.checkNextToken("}");
	}

	private void parseCameraTransform(CameraBuilder camera)
			throws ParserException, IOException {
		if (p.peekNextToken("steps")) {
			int n = p.getNextInt();
			for (int i = 0; i < n; i++)
				parseCameraMatrix(i, camera);
		} else
			parseCameraMatrix(-1, camera);
	}

	private void parseCameraMatrix(int index, CameraBuilder camera)
			throws IOException, ParserException {
		if (p.peekNextToken("transform")) {
			parseMatrix();
		} else {
			if (index >= 0)
				p.checkNextToken("{");
			p.checkNextToken("eye");
			Point3f origin = parsePoint();
			camera.setOrigin(origin);
			p.checkNextToken("target");
			Point3f direction = parsePoint();
			camera.setViewingDirection(new Vector3f(direction.x - origin.x,
					direction.y - origin.y, direction.z - origin.z));
			p.checkNextToken("up");
			camera.setUpVector(parseVector());
			if (index >= 0)
				p.checkNextToken("}");
		}
	}

	private boolean parseShader(SceneDTO renderscene) throws ParserException,
			IOException {
		p.checkNextToken("{");
		p.checkNextToken("name");
		String name = p.getNextToken();
		System.out.println("Reading shader: " + name + " ...");

		p.checkNextToken("type");
		if (p.peekNextToken("diffuse")) {
			DiffuseShader diffShader = new DiffuseShader();
			if (p.peekNextToken("diff")) {

				diffShader.setDiffColor(parseColor());
				renderscene.addShader(name, diffShader);

			} else if (p.peekNextToken("texture")) {

				diffShader.setTexture(p.getNextToken());
				renderscene.addShader(name, diffShader);

			} else
				System.out
						.println("Unrecognized option in diffuse shader block: "
								+ p.getNextToken());
		} else if (p.peekNextToken("phong")) {
			PhongShader phongShader = new PhongShader();
			String tex = null;
			if (p.peekNextToken("texture")) {
				phongShader.setTexturePath(p.getNextToken());
			} else {
				p.checkNextToken("diff");
				phongShader.setDiffColor(parseColor());
			}
			p.checkNextToken("spec");
			phongShader.setSpecColor(parseColor());
			phongShader.setExp(p.getNextFloat());
			if (p.peekNextToken("samples")) {
				phongShader.setSamples(p.getNextInt());
			}
			if (tex != null) {
			} else {
			}
			renderscene.addShader(name, phongShader);
		} else if (p.peekNextToken("amb-occ") || p.peekNextToken("amb-occ2")) {
			if (p.peekNextToken("diff") || p.peekNextToken("bright")) {
				parseColor();
			} else if (p.peekNextToken("texture")) {
				p.getNextToken();
			}
			if (p.peekNextToken("dark")) {
				parseColor();
				p.checkNextToken("samples");
				p.getNextInt();
				p.checkNextToken("dist");
				p.getNextFloat();
			}
		} else if (p.peekNextToken("mirror")) {
			p.checkNextToken("refl");
			MirrorShader mirrorShader = new MirrorShader();
			mirrorShader.setColor(parseColor());
			renderscene.addShader(name, mirrorShader);

		} else if (p.peekNextToken("glass")) {
			p.checkNextToken("eta");
			GlassShader glassShader = new GlassShader();
			glassShader.setEta(p.getNextFloat());
			p.checkNextToken("color");
			glassShader.setColor(parseColor());

			if (p.peekNextToken("absorbtion.distance"))
				glassShader.setAbsorbtionDistance(p.getNextFloat());
			if (p.peekNextToken("absorbtion.color")) {
				glassShader.setAbsorbtionColor(parseColor());
			}

			renderscene.addShader(name, glassShader);

		} else if (p.peekNextToken("shiny")) {
			if (p.peekNextToken("texture")) {
				p.getNextToken();
			} else {
				p.checkNextToken("diff");
				parseColor();
			}
			p.checkNextToken("refl");
			p.getNextFloat();
		} else if (p.peekNextToken("ward")) {
			String tex = null;
			if (p.peekNextToken("texture")) {
				p.getNextToken();
			} else {
				p.checkNextToken("diff");
				parseColor();
			}
			p.checkNextToken("spec");
			parseColor();
			p.checkNextToken("rough");
			p.getNextFloat();
			p.getNextFloat();
			if (p.peekNextToken("samples")) {
				p.getNextInt();
			}
			if (tex != null) {
			} else {
			}
		} else if (p.peekNextToken("view-caustics")) {
		} else if (p.peekNextToken("view-irradiance")) {
		} else if (p.peekNextToken("view-global")) {
		} else if (p.peekNextToken("constant")) {
			p.peekNextToken("color");
			ConstantShader constantShader = new ConstantShader(parseColor());
			renderscene.addShader(name, constantShader);
		} else if (p.peekNextToken("janino")) {
			try {
			} finally {

			}
		} else if (p.peekNextToken("id")) {
		} else if (p.peekNextToken("uber")) {
			if (p.peekNextToken("diff"))
				parseColor();
			if (p.peekNextToken("diff.texture"))
				p.getNextToken();
			if (p.peekNextToken("diff.blend"))
				p.getNextFloat();
			if (p.peekNextToken("refl") || p.peekNextToken("spec"))
				parseColor();
			if (p.peekNextToken("texture")) {
				System.out
						.println("Deprecated uber shader parameter \"texture\" - please use \"diffuse.texture\" and \"diffuse.blend\" instead");
				p.getNextToken();
				p.getNextFloat();
			}
			if (p.peekNextToken("spec.texture"))
				p.getNextToken();
			if (p.peekNextToken("spec.blend"))
				p.getNextFloat();
			if (p.peekNextToken("glossy"))
				p.getNextFloat();
			if (p.peekNextToken("samples")) {
				p.getNextInt();
			}
		} else if (p.peekNextToken("organic")) {
			p.checkNextToken("noNoise");
			Color noNoise = parseColor();
			p.checkNextToken("fullNoise");
			Color fullNoise = parseColor();

			PerlinShader shader = new OrganicShader(noNoise, fullNoise);
			renderscene.addShader(name, shader);
		} else if (p.peekNextToken("stone")) {
			p.checkNextToken("noNoise");
			Color noNoise = parseColor();
			p.checkNextToken("fullNoise");
			Color fullNoise = parseColor();

			PerlinShader shader = new StoneShader(noNoise, fullNoise);
			renderscene.addShader(name, shader);
		} else if (p.peekNextToken("fire")) {
			p.checkNextToken("noNoise");
			Color noNoise = parseColor();
			p.checkNextToken("fullNoise");
			Color fullNoise = parseColor();
			p.checkNextToken("times");
			int times = p.getNextInt();
			p.checkNextToken("persistence");
			float persistence = p.getNextFloat();
			PerlinShader shader = new FireShader(noNoise, fullNoise, times, persistence);
			renderscene.addShader(name, shader);
		} else if (p.peekNextToken("wood")) {
			p.checkNextToken("noNoise");
			Color noNoise = parseColor();
			p.checkNextToken("fullNoise");
			Color fullNoise = parseColor();

			PerlinShader shader = new WoodShader(noNoise, fullNoise);
			renderscene.addShader(name, shader);
		} else if (p.peekNextToken("marble")) {
			p.checkNextToken("noNoise");
			Color noNoise = parseColor();
			p.checkNextToken("fullNoise");
			Color fullNoise = parseColor();

			PerlinShader shader = new MarbleShader(noNoise, fullNoise);
			renderscene.addShader(name, shader);
		} else
			System.out.println("Unrecognized shader type: " + p.getNextToken());
		p.checkNextToken("}");

		return true;
	}

	private void parseObjectBlock(SceneDTO renderscene) throws ParserException,
			IOException {

		p.checkNextToken("{");

		ArrayList<String> shadersArr = new ArrayList<String>();

		boolean noInstance = false;
		Matrix4f transform = null;
		String name = null;
		String[] modifiers = null;
		String shaderName = null;

		if (p.peekNextToken("noinstance")) {
			noInstance = true;
		} else {
			if (p.peekNextToken("shaders")) {
				int n = p.getNextInt();
				for (int i = 0; i < n; i++)
					shadersArr.add(p.getNextToken());
			} else {
				p.checkNextToken("shader");
				shaderName = p.getNextToken();
			}

			if (p.peekNextToken("modifiers")) {
				int n = p.getNextInt();
				modifiers = new String[n];
				for (int i = 0; i < n; i++)
					modifiers[i] = p.getNextToken();
			} else if (p.peekNextToken("modifier"))
				modifiers = new String[] { p.getNextToken() };

			if (p.peekNextToken("transform")) {
				transform = parseMatrix();
			}
		}
		if (p.peekNextToken("accel")) {
			p.getNextToken();
		}
		p.checkNextToken("type");
		String type = p.getNextToken();
		if (p.peekNextToken("name"))
			name = p.getNextToken();
		else {
		}
		if (type.equals("mesh")) {

			LinkedList<Triangle> triangleList = new LinkedList<Triangle>();
			int numVertices = p.getNextInt();
			int numTriangles = p.getNextInt();
			int a, b, c;
			float[] points = new float[numVertices * 3];
			float[] normals = new float[numVertices * 3];
			float[] uvs = new float[numVertices * 2];

			for (int i = 0; i < numVertices; i++) {
				p.checkNextToken("v");
				points[3 * i + 0] = p.getNextFloat();
				points[3 * i + 1] = p.getNextFloat();
				points[3 * i + 2] = p.getNextFloat();

				normals[3 * i + 0] = p.getNextFloat();
				normals[3 * i + 1] = p.getNextFloat();
				normals[3 * i + 2] = p.getNextFloat();
				uvs[2 * i + 0] = p.getNextFloat();
				uvs[2 * i + 1] = p.getNextFloat();
			}

			Point3f x, y, z, nx, ny, nz;
			float[] u = new float[3];
			float[] v = new float[3];
			for (int i = 0; i < numTriangles; i++) {
				p.checkNextToken("t");

				a = p.getNextInt();
				b = p.getNextInt();
				c = p.getNextInt();

				x = new Point3f(points[3 * a + 0], points[3 * a + 1],
						points[3 * a + 2]);
				y = new Point3f(points[3 * b + 0], points[3 * b + 1],
						points[3 * b + 2]);
				z = new Point3f(points[3 * c + 0], points[3 * c + 1],
						points[3 * c + 2]);
				nx = new Point3f(normals[3 * a + 0], normals[3 * a + 1],
						normals[3 * a + 2]);
				ny = new Point3f(normals[3 * b + 0], normals[3 * b + 1],
						normals[3 * b + 2]);
				nz = new Point3f(normals[3 * c + 0], normals[3 * c + 1],
						normals[3 * c + 2]);

				u[0] = uvs[2 * a];
				u[1] = uvs[2 * b];
				u[2] = uvs[2 * c];
				v[0] = uvs[2 * a + 1];
				v[1] = uvs[2 * b + 1];
				v[2] = uvs[2 * c + 1];

				// triangleList.add(new Triangle(x, y, z));
				triangleList.add(new Triangle(x, y, z, nx, ny, nz, u, v,
						intersectionFactory));

			}

			TriangleMesh meshObject;

			meshObject = new TriangleMesh(triangleList, intersectionFactory);
			if (shaderName != null)
				meshObject.setShader(renderscene.getShaders().get(shaderName));
			renderscene.addShape(meshObject);

		} else if (type.equals("flat-mesh")) {
			System.out.println("Ignoring flat mesh...");
			int numVertices = p.getNextInt();
			int numTriangles = p.getNextInt();
			Point3f[] points = new Point3f[numVertices];
			for (int i = 0; i < numVertices; i++) {
				p.checkNextToken("v");
				points[i] = new Point3f(p.getNextFloat(), p.getNextFloat(), p
						.getNextFloat());
				p.getNextFloat();
				p.getNextFloat();
				p.getNextFloat();
				p.getNextFloat();
				p.getNextFloat();
			}
			for (int i = 0; i < numTriangles; i++) {
				p.checkNextToken("t");
				p.getNextInt();
				p.getNextInt();
				p.getNextInt();
			}

		} else if (type.equals("sphere")) {
			System.out.println("Reading sphere ...");

			Sphere aSphere = new Sphere(intersectionFactory);
			if (transform == null && !noInstance) {
				p.checkNextToken("c");
				Point3f center = parsePoint();
				p.checkNextToken("r");
				float radius = p.getNextFloat();
				aSphere.setCenter(center);
				aSphere.setRadius(radius);

				noInstance = true;
			} else {
				aSphere.setTransformation(transform);
			}

			System.out.println("Adding sphere to scene..");
			if (shaderName != null)
				aSphere.setShader(renderscene.getShaders().get(shaderName));
			renderscene.addShape(aSphere);

		} else if (type.equals("box")) {

			System.out.println("Reading box ...");
			Box box = new Box(intersectionFactory);

			if (transform != null) {
				box.setTransformation(transform);
			}

			if (shaderName != null) {
				box.setShader(renderscene.getShaders().get(shaderName));
			}

			renderscene.addShape(box);

		} else if (type.equals("banchoff")) {
			System.out.println("Reading banchoff ...");
		} else if (type.equals("torus")) {
			System.out.println("Reading torus ...");
			p.checkNextToken("r");
			p.getNextFloat();
			p.getNextFloat();
		} else if (type.equals("plane")) {
			System.out.println("Reading plane ...");
			p.checkNextToken("p");
			Point3f center = parsePoint();
			Plane plane = null;
			if (p.peekNextToken("n")) {
				Vector3f normal = parseVector();

				plane = new Plane(center, normal, new IntersectionFactory());
			} else {
				p.checkNextToken("p");
				Point3f point1 = parsePoint();
				p.checkNextToken("p");
				Point3f point2 = parsePoint();
				plane = new Plane(center, point1, point2, intersectionFactory);

			}

			if (shaderName != null)
				plane.setShader(renderscene.getShaders().get(shaderName));

			System.out.println("Adding plane to scene..");
			renderscene.addPlane(plane);

		} else if (type.equals("cornellbox")) {
			System.out.println("Reading cornell box ...");
			if (transform != null)
				System.out
						.println("Instancing is not supported on cornell box -- ignoring transform");
			p.checkNextToken("corner0");
			parsePoint();
			p.checkNextToken("corner1");
			parsePoint();
			p.checkNextToken("left");
			parseColor();
			p.checkNextToken("right");
			parseColor();
			p.checkNextToken("top");
			parseColor();
			p.checkNextToken("bottom");
			parseColor();
			p.checkNextToken("back");
			parseColor();
			p.checkNextToken("emit");
			parseColor();
			if (p.peekNextToken("samples")) {
				p.getNextInt();
			}
			noInstance = true;
		} else if (type.equals("generic-mesh")) {
			// parse vertices
			p.checkNextToken("points");
			int np = p.getNextInt();
			float[] points = parseFloatArray(np * 3);
			// parse triangle indices
			p.checkNextToken("triangles");
			int nt = p.getNextInt();
			int[] triArray = parseIntArray(nt * 3);
			// parse normals
			int a, b, c;
			LinkedList<Triangle> triangleList = new LinkedList<Triangle>();

			float[] normalPoints = null;
			p.checkNextToken("normals");
			if (p.peekNextToken("vertex")) {
				System.out.println("Parsing normals...");
				normalPoints = parseFloatArray(np * 3);
			} else if (p.peekNextToken("facevarying"))
				parseFloatArray(nt * 9);
			else
				p.checkNextToken("none");

			// parse texture coordinates
			float[] uvs = null;
			System.out.println("Parsing UVs");
			p.checkNextToken("uvs");
			if (p.peekNextToken("vertex")) {
				uvs = parseFloatArray(np * 2);
			} else if (p.peekNextToken("facevarying"))
				uvs = parseFloatArray(nt * 6);
			else
				p.checkNextToken("none");
			if (p.peekNextToken("face_shaders"))
				parseIntArray(nt);

			Point3f x, y, z, nx, ny, nz;
			float[] u = new float[3];
			float[] v = new float[3];
			for (int i = 0; i < nt; i++) {
				//              
				a = triArray[i * 3 + 0];
				b = triArray[i * 3 + 1];
				c = triArray[i * 3 + 2];

				x = new Point3f(points[3 * a + 0], points[3 * a + 1],
						points[3 * a + 2]);
				y = new Point3f(points[3 * b + 0], points[3 * b + 1],
						points[3 * b + 2]);
				z = new Point3f(points[3 * c + 0], points[3 * c + 1],
						points[3 * c + 2]);

				if (normalPoints == null && uvs == null) {
					triangleList
							.add(new Triangle(x, y, z, intersectionFactory));
				} else if (normalPoints != null && uvs == null) {
					nx = new Point3f(normalPoints[3 * a + 0],
							normalPoints[3 * a + 1], normalPoints[3 * a + 2]);
					ny = new Point3f(normalPoints[3 * b + 0],
							normalPoints[3 * b + 1], normalPoints[3 * b + 2]);
					nz = new Point3f(normalPoints[3 * c + 0],
							normalPoints[3 * c + 1], normalPoints[3 * c + 2]);

					u[0] = 0;
					u[1] = 0;
					u[2] = 0;
					v[0] = 0;
					v[1] = 0;
					v[2] = 0;
					// Add triangle with normals
					triangleList.add(new Triangle(x, y, z, nx, ny, nz, u, v,
							intersectionFactory));
				} else if (normalPoints == null && uvs != null) {
					u[0] = uvs[2 * a];
					u[1] = uvs[2 * b];
					u[2] = uvs[2 * c];
					v[0] = uvs[2 * a + 1];
					v[1] = uvs[2 * b + 1];
					v[2] = uvs[2 * c + 1];

					// Add triangle with UVs
					triangleList.add(new Triangle(x, y, z, u, v,
							intersectionFactory));
				} else {
					nx = new Point3f(normalPoints[3 * a + 0],
							normalPoints[3 * a + 1], normalPoints[3 * a + 2]);
					ny = new Point3f(normalPoints[3 * b + 0],
							normalPoints[3 * b + 1], normalPoints[3 * b + 2]);
					nz = new Point3f(normalPoints[3 * c + 0],
							normalPoints[3 * c + 1], normalPoints[3 * c + 2]);
					u[0] = uvs[2 * a];
					u[1] = uvs[2 * b];
					u[2] = uvs[2 * c];
					v[0] = uvs[2 * a + 1];
					v[1] = uvs[2 * b + 1];
					v[2] = uvs[2 * c + 1];

					// Add triangle with normals and UVs
					triangleList.add(new Triangle(x, y, z, nx, ny, nz, u, v,
							intersectionFactory));
				}
			}

			TriangleMesh meshObject = new TriangleMesh(triangleList,
					intersectionFactory);

			if (transform != null) {
				meshObject.setTransformation(transform);
			}

			if (shaderName != null)
				meshObject.setShader(renderscene.getShaders().get(shaderName));

			renderscene.addShape(meshObject);

		} else if (type.equals("hair")) {
			System.out.println("Reading hair curves: " + name + " ... ");
			p.checkNextToken("segments");
			p.getNextInt();
			p.checkNextToken("width");
			p.getNextFloat();
			p.checkNextToken("points");
			parseFloatArray(p.getNextInt());
		} else if (type.equals("janino-tesselatable")) {
			System.out.println("Reading procedural primitive: " + name
					+ " ... ");
			p.getNextCodeBlock();
			try {
			} finally {

			}
		} else if (type.equals("teapot")) {
			System.out.println("Reading teapot: " + name + " ... ");
			boolean hasTesselationArguments = false;
			if (p.peekNextToken("subdivs")) {
				p.getNextInt();
				hasTesselationArguments = true;
			}
			if (p.peekNextToken("smooth")) {
				p.getNextBoolean();
				hasTesselationArguments = true;
			}
			if (hasTesselationArguments) {
			} else {
			}
		} else if (type.equals("gumbo")) {
			System.out.println("Reading gumbo: " + name + " ... ");
			boolean hasTesselationArguments = false;
			if (p.peekNextToken("subdivs")) {
				p.getNextInt();
				hasTesselationArguments = true;
			}
			if (p.peekNextToken("smooth")) {
				p.getNextBoolean();
				hasTesselationArguments = true;
			}
			if (hasTesselationArguments) {
			} else {
			}
		} else if (type.equals("julia")) {
			System.out.println("Reading julia fractal: " + name + " ... ");
			if (p.peekNextToken("q")) {
				p.getNextFloat();
				p.getNextFloat();
				p.getNextFloat();
				p.getNextFloat();
			}
			if (p.peekNextToken("iterations"))
				p.getNextInt();
			if (p.peekNextToken("epsilon")) {
				p.getNextFloat();
			}
		} else if (type.equals("particles") || type.equals("dlasurface")) {
			if (type.equals("dlasurface"))
				System.out
						.println("Deprecated object type: \"dlasurface\" - please use \"particles\" instead");
			p.checkNextToken("filename");
			String filename = p.getNextToken();
			boolean littleEndian = false;
			if (p.peekNextToken("little_endian"))
				littleEndian = true;
			System.out.println("Loading particle file: " + filename);
			File file = new File(filename);
			FileInputStream stream = new FileInputStream(filename);
			MappedByteBuffer map = stream.getChannel().map(
					FileChannel.MapMode.READ_ONLY, 0, file.length());
			if (littleEndian)
				map.order(ByteOrder.LITTLE_ENDIAN);
			FloatBuffer buffer = map.asFloatBuffer();
			float[] data = new float[buffer.capacity()];
			for (int i = 0; i < data.length; i++)
				data[i] = buffer.get(i);
			stream.close();
			if (p.peekNextToken("num")) {
				p.getNextInt();
			} else {
			}
			p.checkNextToken("radius");
			p.getNextFloat();
		} else if (type.equals("file-mesh")) {
			System.out.println("Reading file mesh: " + name + " ... ");
			p.checkNextToken("filename");
			p.getNextToken();
			if (p.peekNextToken("smooth_normals")) {
				p.getNextBoolean();
			}
		} else if (type.equals("bezier-mesh")) {
			System.out.println("Reading bezier mesh: " + name + " ... ");
			p.checkNextToken("n");
			int nu = p.getNextInt();
			int nv = p.getNextInt();
			if (p.peekNextToken("wrap")) {
				p.getNextBoolean();
				p.getNextBoolean();
			}
			p.checkNextToken("points");
			float[] points = new float[3 * nu * nv];
			for (int i = 0; i < points.length; i++)
				points[i] = p.getNextFloat();
			if (p.peekNextToken("subdivs"))
				p.getNextInt();
			if (p.peekNextToken("smooth")) {
				p.getNextBoolean();
			}
		} else {
			System.out.println("Unrecognized object type: " + p.getNextToken());
			noInstance = true;
		}
		if (!noInstance) {
			if (modifiers != null)
				if (transform != null) {
				}
		}
		p.checkNextToken("}");

	}

	private void parseLightBlock(SceneDTO renderscene) throws ParserException,
			IOException {
		p.checkNextToken("{");
		p.checkNextToken("type");
		if (p.peekNextToken("mesh")) {
			System.out.println("Deprecated light type: mesh");
			p.checkNextToken("name");
			String name = p.getNextToken();
			System.out.println("Ignoring light mesh: " + name + " ...");
			p.checkNextToken("emit");
			parseColor();
			int samples = numLightSamples;
			if (p.peekNextToken("samples"))
				samples = p.getNextInt();
			else
				System.out.println("Samples keyword not found - defaulting to "
						+ samples);
			int numVertices = p.getNextInt();
			int numTriangles = p.getNextInt();
			float[] points = new float[3 * numVertices];
			int[] triangles = new int[3 * numTriangles];
			for (int i = 0; i < numVertices; i++) {
				p.checkNextToken("v");
				points[3 * i + 0] = p.getNextFloat();
				points[3 * i + 1] = p.getNextFloat();
				points[3 * i + 2] = p.getNextFloat();
				p.getNextFloat();
				p.getNextFloat();
				p.getNextFloat();
				p.getNextFloat();
				p.getNextFloat();
			}
			for (int i = 0; i < numTriangles; i++) {
				p.checkNextToken("t");
				triangles[3 * i + 0] = p.getNextInt();
				triangles[3 * i + 1] = p.getNextInt();
				triangles[3 * i + 2] = p.getNextInt();
			}
		} else if (p.peekNextToken("point")) {
			System.out.println("Reading point light ...");
			Color pow;
			float po = 100;
			if (p.peekNextToken("color")) {
				pow = parseColor();
				p.checkNextToken("power");
				po = p.getNextFloat();
			} else {
				System.out
						.println("Deprecated color specification - please use color and power instead");
				p.checkNextToken("power");
				pow = parseColor();
			}
			p.checkNextToken("p");
			PointLight light = new PointLight(parsePoint(),
					intersectionFactory, pow, po);
			renderscene.addLight(light);

		} else if (p.peekNextToken("spherical")) {
			System.out.println("Ignoring spherical light ...");
			p.checkNextToken("color");
			parseColor();
			p.checkNextToken("radiance");
			p.getNextFloat();
			p.checkNextToken("center");
			parsePoint();
			p.checkNextToken("radius");
			p.getNextFloat();
			p.checkNextToken("samples");
			p.getNextInt();
		} else if (p.peekNextToken("directional")) {
			System.out.println("Ignoring directional light ...");
			p.checkNextToken("source");
			parsePoint();
			p.checkNextToken("target");
			parsePoint();
			p.checkNextToken("radius");
			p.getNextFloat();
			p.checkNextToken("emit");
			parseColor();
			if (p.peekNextToken("intensity"))
				p.getNextFloat();
		} else if (p.peekNextToken("ibl")) {
			System.out.println("Ignoring image based light ...");
			p.checkNextToken("image");
			p.getNextToken();
			p.checkNextToken("center");
			parseVector();
			p.checkNextToken("up");
			parseVector();
			p.checkNextToken("lock");
			p.getNextBoolean();
			if (p.peekNextToken("samples"))
				p.getNextInt();
		} else if (p.peekNextToken("meshlight")) {
			p.checkNextToken("name");
			p.getNextToken();
			System.out.println("Ignoring meshlight...");
			p.checkNextToken("emit");
			parseColor();
			if (p.peekNextToken("radiance"))
				p.getNextFloat();
			if (p.peekNextToken("samples"))
				p.getNextInt();
			p.checkNextToken("points");
			int np = p.getNextInt();
			parseFloatArray(np * 3);
			p.checkNextToken("triangles");
			int nt = p.getNextInt();
			parseIntArray(nt * 3);
		} else if (p.peekNextToken("sunsky")) {
			p.checkNextToken("up");
			parseVector();
			p.checkNextToken("east");
			parseVector();
			p.checkNextToken("sundir");
			parseVector();
			p.checkNextToken("turbidity");
			p.getNextFloat();
			if (p.peekNextToken("samples")) {
				p.getNextInt();
			}
		} else
			System.out.println("Unrecognized object type: " + p.getNextToken());
		p.checkNextToken("}");
	}

	private Color parseColor() throws IOException, ParserException {
		if (p.peekNextToken("{")) {
			String space = p.getNextToken();
			Color c = null;
			if (space.equals("sRGB nonlinear")) {

				float r = p.getNextFloat();
				float g = p.getNextFloat();
				float b = p.getNextFloat();
				// r = transformer.ungammaCorrect(r);
				// g = transformer.ungammaCorrect(r);
				// b = transformer.ungammaCorrect(r);
				c = new Color(r, g, b);
			} else if (space.equals("sRGB linear")) {
				float r = p.getNextFloat();
				float g = p.getNextFloat();
				float b = p.getNextFloat();
				c = new Color(r, g, b);
			} else
				System.out.println("Unrecognized color space: " + space);
			p.checkNextToken("}");
			return c;
		} else {
			float r = p.getNextFloat();
			float g = p.getNextFloat();
			float b = p.getNextFloat();
			return new Color(r, g, b);
		}
	}

	private static class RGBTransformer {
		private final float gamma, breakPoint;
		private final float slope, slopeMatch, segmentOffset;

		private RGBTransformer(float gamma, float breakPoint) {
			this.gamma = gamma;
			this.breakPoint = breakPoint;

			if (breakPoint > 0) {
				slope = 1 / (gamma
						/ (float) Math.pow(breakPoint, 1 / gamma - 1) - gamma
						* breakPoint + breakPoint);
				slopeMatch = gamma * slope
						/ (float) Math.pow(breakPoint, 1 / gamma - 1);
				segmentOffset = slopeMatch
						* (float) Math.pow(breakPoint, 1 / gamma) - slope
						* breakPoint;
			} else {
				slope = 1;
				slopeMatch = 1;
				segmentOffset = 0;
			}
		}

		public final float ungammaCorrect(float vp) {
			if (vp <= 0)
				return 0;
			else if (vp >= 1)
				return 1;
			else if (vp <= breakPoint * slope)
				return vp / slope;
			else
				return (float) Math.pow((vp + segmentOffset) / slopeMatch,
						gamma);
		}
	}

	/* Matrix parsing and helpers */
	// TODO: Move me to a helper class

	private Point3f parsePoint() throws IOException {
		float x = p.getNextFloat();
		float y = p.getNextFloat();
		float z = p.getNextFloat();
		return new Point3f(x, y, z);
	}

	private Vector3f parseVector() throws IOException {
		float x = p.getNextFloat();
		float y = p.getNextFloat();
		float z = p.getNextFloat();
		return new Vector3f(x, y, z);
	}

	private int[] parseIntArray(int size) throws IOException {
		int[] data = new int[size];
		for (int i = 0; i < size; i++)
			data[i] = p.getNextInt();
		return data;
	}

	private float[] parseFloatArray(int size) throws IOException {
		float[] data = new float[size];
		for (int i = 0; i < size; i++)
			data[i] = p.getNextFloat();
		return data;
	}

	private float[] convertToDoubleArray(float[] floatArray) {

		float[] result = new float[floatArray.length];
		for (int i = 0; i < floatArray.length; i++) {
			result[i] = floatArray[i];
		}

		return result;
	}

	private Matrix4f newIdentityMatrix4f() {
		Matrix4f result = new Matrix4f(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
				0, 0, 1);
		return result;
	}

	private Matrix4f newTranslationMatrix4f(float x, float y, float z) {
		Matrix4f m = new Matrix4f();
		m.m00 = m.m11 = m.m22 = m.m33 = 1;
		m.m03 = x;
		m.m13 = y;
		m.m23 = z;
		return m;
	}

	public Matrix4f newScaledMatrix4f(float s) {
		Matrix4f m = new Matrix4f();
		m.m00 = m.m11 = m.m22 = s;
		m.m33 = 1;
		return m;
	}

	public Matrix4f newScaledMatrix4f(float x, float y, float z) {
		Matrix4f m = new Matrix4f();
		m.m00 = x;
		m.m11 = y;
		m.m22 = z;
		m.m33 = 1;
		return m;
	}

	private Matrix4f rotateX(float theta) {
		Matrix4f m = new Matrix4f();
		float s = (float) Math.sin(theta);
		float c = (float) Math.cos(theta);
		m.m00 = m.m33 = 1;
		m.m11 = m.m22 = c;
		m.m12 = -s;
		m.m21 = +s;
		return m;
	}

	/**
	 * Creates a rotation matrix about the Y axis.
	 * 
	 * @param theta
	 *            angle to rotate about the Y axis in radians
	 * @return a new Matrix4 object representing the rotation
	 */
	private Matrix4f rotateY(float theta) {
		Matrix4f m = new Matrix4f();
		float s = (float) Math.sin(theta);
		float c = (float) Math.cos(theta);
		m.m11 = m.m33 = 1;
		m.m00 = m.m22 = c;
		m.m02 = +s;
		m.m20 = -s;
		return m;
	}

	/**
	 * Creates a rotation matrix about the Z axis.
	 * 
	 * @param theta
	 *            angle to rotate about the Z axis in radians
	 * @return a new Matrix4 object representing the rotation
	 */
	private Matrix4f rotateZ(float theta) {
		Matrix4f m = new Matrix4f();
		float s = (float) Math.sin(theta);
		float c = (float) Math.cos(theta);
		m.m22 = m.m33 = 1;
		m.m00 = m.m11 = c;
		m.m01 = -s;
		m.m10 = +s;
		return m;
	}

	/**
	 * Creates a rotation matrix about the specified axis. The axis vector need
	 * not be normalized.
	 * 
	 * @param x
	 *            x component of the axis vector
	 * @param y
	 *            y component of the axis vector
	 * @param z
	 *            z component of the axis vector
	 * @param theta
	 *            angle to rotate about the axis in radians
	 * @return a new Matrix4 object representing the rotation
	 */
	private Matrix4f rotate(float x, float y, float z, float theta) {
		Matrix4f m = new Matrix4f();
		float invLen = 1 / (float) Math.sqrt(x * x + y * y + z * z);
		x *= invLen;
		y *= invLen;
		z *= invLen;
		float s = (float) Math.sin(theta);
		float c = (float) Math.cos(theta);
		float t = 1 - c;
		m.m00 = t * x * x + c;
		m.m11 = t * y * y + c;
		m.m22 = t * z * z + c;
		float txy = t * x * y;
		float sz = s * z;
		m.m01 = txy - sz;
		m.m10 = txy + sz;
		float txz = t * x * z;
		float sy = s * y;
		m.m02 = txz + sy;
		m.m20 = txz - sy;
		float tyz = t * y * z;
		float sx = s * x;
		m.m12 = tyz - sx;
		m.m21 = tyz + sx;
		m.m33 = 1;
		return m;
	}

	private Matrix4f parseMatrix() throws IOException, ParserException {
		if (p.peekNextToken("row")) {
			return new Matrix4f(parseFloatArray(16));
		} else if (p.peekNextToken("col")) {
			Matrix4f a = (new Matrix4f(parseFloatArray(16)));
			a.transpose();
			return a;
		} else {
			Matrix4f m = newIdentityMatrix4f();
			p.checkNextToken("{");
			while (!p.peekNextToken("}")) {
				Matrix4f t = null;
				if (p.peekNextToken("translate")) {
					float x = p.getNextFloat();
					float y = p.getNextFloat();
					float z = p.getNextFloat();
					t = newTranslationMatrix4f(x, y, z);
				} else if (p.peekNextToken("scaleu")) {
					float s = p.getNextFloat();
					t = newScaledMatrix4f(s);
				} else if (p.peekNextToken("scale")) {
					float x = p.getNextFloat();
					float y = p.getNextFloat();
					float z = p.getNextFloat();
					t = newScaledMatrix4f(x, y, z);
				} else if (p.peekNextToken("scalex")) {
					float x = p.getNextFloat();
					t = newScaledMatrix4f(x, 1, 1);
				} else if (p.peekNextToken("scaley")) {
					float y = p.getNextFloat();
					t = newScaledMatrix4f(1, y, 1);
				} else if (p.peekNextToken("scalez")) {
					float z = p.getNextFloat();
					t = newScaledMatrix4f(1, 1, z);
				} else if (p.peekNextToken("rotatex")) {
					float angle = p.getNextFloat();
					t = rotateX((float) Math.toRadians(angle));
				} else if (p.peekNextToken("rotatey")) {
					float angle = p.getNextFloat();
					t = rotateY((float) Math.toRadians(angle));
				} else if (p.peekNextToken("rotatez")) {
					float angle = p.getNextFloat();
					t = rotateZ((float) Math.toRadians(angle));
				} else if (p.peekNextToken("rotate")) {
					float x = p.getNextFloat();
					float y = p.getNextFloat();
					float z = p.getNextFloat();
					float angle = p.getNextFloat();
					t = rotate(x, y, z, (float) Math.toRadians(angle));
				} else
					System.out.println("Unrecognized transformation type: "
							+ p.getNextToken());
				if (t != null)
					t.mul(m);
				m = t;
			}
			return m;
		}
	}
}
